﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse.Net
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;
    using System.Text;

    /// <summary>Is the base class for all pipelinable single-line commands.</summary>
    /// <typeparam name="TClient">The type of the client the command is for.</typeparam>
    /// <typeparam name="TResponse">The type of the response the command will produce.</typeparam>
    /// <remarks>Concrete commands never derive directly from this class, but subclass protocol-specific variants, like
    /// e.g. <see cref="Nntp.SingleLineCommand{T, U, V, W, X}"/>.</remarks>
    /// <threadsafety static="true" instance="false"/>
    public abstract class SingleLineCommandBase<TClient, TResponse> : PipelinableCommandBase<TClient, TResponse>
        where TClient : IClient<TClient>
        where TResponse : IResponse
    {
        private readonly byte[] command;
        private readonly Encoding initialResponseLineEncoding;
        private readonly int maxInitialResponseLineWordCount;
        private readonly string[] successResponseCodes;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        internal SingleLineCommandBase(
            byte[] command,
            Encoding initialResponseLineEncoding,
            int maxInitialResponseLineWordCount,
            params string[] successResponseCodes)
        {
            this.command = command;
            this.initialResponseLineEncoding = initialResponseLineEncoding;
            this.maxInitialResponseLineWordCount = maxInitialResponseLineWordCount;
            this.successResponseCodes = successResponseCodes;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>When overridden in a derived class creates and returns a <typeparamref name="TResponse"/> object
        /// from <paramref name="responseStream"/>.</summary>
        /// <remarks>It is assumed that overrides throw either <see cref="ArgumentException"/>,
        /// <see cref="FormatException"/> or <see cref="OverflowException"/> when they fail to parse data from
        /// <paramref name="responseStream"/>, see <see cref="ReceiveResponse"/> for more information.</remarks>
        protected abstract TResponse CreateResponse(ResponseStream responseStream);

        /// <summary>See <see cref="CommandBase{T, U}.SendCommand"/>.</summary>
        /// <exception cref="IOException">A general I/O error occurred, see <see cref="Exception.InnerException"/>
        /// for more information.</exception>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "Method is not supposed to be called by external code.")]
        protected sealed override void SendCommand(CreateCommandStreamCallback createCommandStreamCallback)
        {
            using (var stream = createCommandStreamCallback(false))
            {
                stream.Write(this.command, 0, this.command.Length);
            }
        }

        /// <summary>See <see cref="PipelinableCommandBase{T, U}.ReceiveResponse"/>.</summary>
        /// <exception cref="ClientBaseException">The response code received from the server indicates a known error
        /// condition, see the <see cref="CommandBase{T, U}.ThrowOnError"/> override for information on which
        /// <see cref="ClientBaseException"/> subtype is thrown for a given response code.</exception>
        /// <exception cref="IOException">A general I/O error occurred, see <see cref="Exception.InnerException"/>
        /// for more information.</exception>
        /// <exception cref="UnexpectedResponseException">The response received from the server was neither led by a
        /// response code indicating a known error (see <see cref="ICommand{T}.ThrowOnError"/> implementation) nor was
        /// it equal to one of the success codes for the command; or <see cref="CreateResponse"/>
        /// threw <see cref="ArgumentException"/>, <see cref="FormatException"/> or <see cref="OverflowException"/>.
        /// </exception>
        /// <remarks>Creates a <see cref="ResponseStream"/> object by calling
        /// <paramref name="createResponseStreamCallback"/> with the arguments passed to
        /// <see cref="M:SingleLineCommandBase"/>. Then calls <see cref="CreateResponse"/> with the
        /// <see cref="ResponseStream"/> object and returns the result.</remarks>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "Method is not supposed to be called by external code.")]
        protected sealed override TResponse ReceiveResponse(CreateResponseStreamCallback createResponseStreamCallback)
        {
            ResponseStream responseStream = createResponseStreamCallback(
                this.initialResponseLineEncoding, this.maxInitialResponseLineWordCount, this.successResponseCodes);

            try
            {
                return this.CreateResponse(responseStream);
            }
            catch (Exception ex)
            {
                responseStream.Dispose();

                if (ex is ArgumentException || ex is FormatException || ex is OverflowException)
                {
                    throw new ExceptionHelper(
                        responseStream.InitialLineWords).CreateException<UnexpectedResponseException>(ex);
                }

                throw;
            }
        }
    }
}
